package com.abiao.jpushdemo.component;

import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jpush.api.JPushClient;
import cn.jpush.api.push.PushResult;
import cn.jpush.api.push.model.Options;
import cn.jpush.api.push.model.Platform;
import cn.jpush.api.push.model.PushPayload;
import cn.jpush.api.push.model.audience.Audience;
import cn.jpush.api.push.model.notification.AndroidNotification;
import cn.jpush.api.push.model.notification.IosAlert;
import cn.jpush.api.push.model.notification.IosNotification;
import cn.jpush.api.push.model.notification.Notification;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;

/**
 * @program: jpush-demo
 * @description: 极光推送-服务端api调用封装
 * @author: luobiao
 * @date: 2022-05-07 10:46
 **/
@Slf4j
@Component
public class MyJpushClient {

    @Value("${jpush.appKey}")
    private String appKey;

    @Value("${jpush.masterSecret}")
    private String masterSecret;

    @Value("${jpush.apnsProduction}")
    private boolean apnsProduction;

    @Resource
    private RestTemplate restTemplate;

    /**
     * 推送成功状态码
     */
    private static final int RESPONSE_OK = 200;
    /**
     * 离线消息保留时长 (秒)，不保留离线消息：0， 1天：86400，3天：259200，10天（vip）：864000
     */
    private static final long TIME_TO_LIVE = 259200L;
    private static JPushClient jPushClient = null;
    private static HttpHeaders headers = null;

    /**
     * 极光api接口url
     */
    private static final String DEVICE_API_ADDRESS  = "https://device.jpush.cn";
    private static final String V3_DEVICES_URL = "/v3/devices";
    private static final String V3_ALIASES_URL = "/v3/aliases";

    /**
     * 获取极光推送客户端对象
     * @return 极光推送客户端对象
     */
    public JPushClient getJpushClient() {
        if (jPushClient == null) {
            jPushClient = new JPushClient(masterSecret, appKey);
        }
        return jPushClient;
    }

    /**
     * 极光api请求头信息封装
     * @return 请求头对象
     */
    private HttpHeaders getHttpHeaders(){
        if (headers == null) {
            //封装极光api请求头
            headers = new HttpHeaders();
            headers.add("Authorization",this.getBasicAuthorization());
            List<MediaType> accept = new ArrayList<>();
            accept.add(MediaType.APPLICATION_JSON);
            headers.setAccept(accept);
        }
        return headers;
    }

    /**
     * 获取鉴权字符串
     * 算法：base64(appKey:masterSecret)
     * @return 极光api鉴权字符串
     */
    private String getBasicAuthorization() {
        String encodeKey = appKey + ":" + masterSecret;
        return "Basic " + Base64.getEncoder().encodeToString(encodeKey.getBytes());
    }

    /**
     * 查询别名
     * @param alias 别名
     * @return 接口返回对象（jsonString）
     */
    public String getAliasDevice(String alias){
        return getOrDeleteAliasDevice(alias, HttpMethod.GET);
    }

    /**
     * 删除别名
     * @param alias 别名
     * @return 接口返回对象（jsonString）
     */
    public String deleteAliasDevice(String alias){
        return getOrDeleteAliasDevice(alias, HttpMethod.DELETE);
    }

    /**
     * 查询/删除别名设备列表
     * @param alias 别名
     * @param httpMethod 接口请求类型 get：获取别名注册id列表，delete：删除别名
     * @return 接口返回对象（jsonString）
     */
    private String getOrDeleteAliasDevice(String alias,HttpMethod httpMethod){
        HttpEntity<String> entity = new HttpEntity<>(null,getHttpHeaders());
        HashMap<String, String> paramsMap = new HashMap<String, String>(1);
        paramsMap.put("platform","android,ios");
        String url = DEVICE_API_ADDRESS + V3_ALIASES_URL + "/" + alias;
        log.info("request url :{}",url);
        log.info("request querry params :{}",JSON.toJSONString(paramsMap));
        ResponseEntity<String> responseEntity = null;
        try {
            //远程调用接口
            responseEntity = restTemplate.exchange(url, httpMethod, entity, String.class,paramsMap);
            if (responseEntity!=null&&responseEntity.getStatusCodeValue()==RESPONSE_OK){
                log.info("极光" + (HttpMethod.GET==httpMethod?"查询":"删除") + "别名成功!");
                return responseEntity.getBody();
            }
        }catch (Exception e){
            log.error("极光" + (HttpMethod.GET==httpMethod?"查询":"删除") + "别名失败，错误消息：{}",e.getMessage());
        }
        return null;
    }

    /**
     * 解绑别名与设备绑定关系
     * @param alias 别名
     * @param registrationId 极光注册id
     * @return 接口返回对象（jsonString）
     */
    public String unbindAliasAndDevice(String alias, String registrationId){
        //请求体
        JSONObject body = new JSONObject();
        //registration_ids
        JSONObject registrationIds = new JSONObject();
        //remove
        JSONArray remove = new JSONArray();
        remove.add(registrationId);
        registrationIds.put("remove",remove);
        body.put("registration_ids",registrationIds);
        HttpEntity<String> entity = new HttpEntity<>(JSON.toJSONString(body), getHttpHeaders());
        String url = DEVICE_API_ADDRESS + V3_ALIASES_URL + "/" + alias;
        log.info("request url :{}",url);
        log.info("request body :{}",JSON.toJSONString(body));
        ResponseEntity<String> responseEntity = null;
        try {
            //远程调用接口
            responseEntity = restTemplate.postForEntity(url, entity, String.class);
            if (responseEntity!=null&&responseEntity.getStatusCodeValue()==RESPONSE_OK){
                log.info("极光解绑别名与设备成功!");
                return responseEntity.getBody();
            }
        }catch (Exception e){
            log.error("极光解绑别名与设备失败，错误消息：{}",e.getMessage());
        }
        return null;
    }

    /**
     *  绑定设备与别名
     * @param alias 别名
     * @param registrationId 极光注册id
     * @return 接口返回对象（jsonString）
     */
    public String bindAliasAndDevice(String alias, String registrationId){
        /*
         * 请求体格式
         *  {
         *      "tags":"",//清空该registration_id所有标签
         *      "alias": "alias1",//绑定该registration_id到别名
         *      "mobile":""//清空该registration_id绑定手机号
         *  }
         */
        //请求体
        JSONObject body = new JSONObject();
        body.put("tags","");
        body.put("mobile","");
        body.put("alias",alias);
        HttpEntity<String> entity = new HttpEntity<>(JSON.toJSONString(body), getHttpHeaders());
        String url = DEVICE_API_ADDRESS + V3_DEVICES_URL + "/" + registrationId;
        log.info("request url :{}",url);
        log.info("request body :{}",JSON.toJSONString(body));
        ResponseEntity<String> responseEntity = null;
        try {
            //远程调用接口
            responseEntity = restTemplate.postForEntity(url, entity, String.class);
            if (responseEntity!=null&&responseEntity.getStatusCodeValue()==RESPONSE_OK){
                log.info("极光绑定别名与设备成功!");
                return responseEntity.getBody();
            }
        }catch (Exception e){
            log.error("极光绑定别名与设备失败，错误消息：{}",e.getMessage());
        }
        return null;
    }

    /**
     * 向android和ios平台，指定别名列表推送-通知
     * @param alias 别名列表
     * @param myNotification 通知信息
     */
    public void sendToAliasList(List<String> alias, MyNotification myNotification) {
        PushPayload pushPayload = buildPushObjectAndroidIosAliasListNotification(alias, myNotification);
        this.sendPush(pushPayload);
    }

    /**
     * -------------------------------------------构建推送对象------------------------------------------------
     */

    /**
     * 构建推送对象：向android和ios平台，指定别名列表的用户推送通知
     * @param aliasList 别名列表
     * @param myNotification 通知信息
     * @return 推送对象
     */
    private PushPayload buildPushObjectAndroidIosAliasListNotification(List<String> aliasList, MyNotification myNotification) {
        return PushPayload.newBuilder()
                //推送平台
                .setPlatform(Platform.android_ios())
                //推送目标
                .setAudience(Audience.alias(aliasList))
                //通知
                .setNotification(Notification.newBuilder()
                        //android通知
                        .addPlatformNotification(AndroidNotification.newBuilder()
                                //通知标题
                                .setTitle(myNotification.getTitle())
                                //通知内容
                                .setAlert(myNotification.getAlert())
                                //扩展字段
                                .addExtras(myNotification.getExtras())
                                .build()
                        )
                        // ios通知
                        .addPlatformNotification(IosNotification.newBuilder()
                                .setAlert(IosAlert.newBuilder()
                                        //title:消息标题，subtitle：副标题，body：消息内容
                                        .setTitleAndBody(myNotification.getTitle(),null,myNotification.getAlert())
                                        .build()
                                )
                                //推送的角标（badge）自动加1
                                .incrBadge(1)
                                //提醒声音
                                .setSound("default")
                                //扩展字段
                                .addExtras(myNotification.getExtras())
                                //ios通知扩展
                                .setMutableContent(myNotification.isMutableContent())
                                .build()
                        )
                        .build()
                )
                //可选参数
                .setOptions(Options.newBuilder()
                        //推送的离线保存时长（秒），默认一天，普通用户最长3天，vip最长10天
                        .setTimeToLive(TIME_TO_LIVE)
                        //apns推送环境，false表示开发，true表示生产；该字段仅对iOS的通知有效
                        .setApnsProduction(apnsProduction)
                        .build()
                )
                .build();
    }

    /**
     * -------------------------------------------发送推送------------------------------------------------
     */

    /**
     * 发送推送
     * @param pushPayload 推送对象
     * @return 推送结果
     */
    private PushResult sendPush(PushPayload pushPayload) {
        PushResult pushResult = null;
        try {
            JPushClient jPushClient = this.getJpushClient();
            pushResult = jPushClient.sendPush(pushPayload);
            if (pushResult.getResponseCode() == RESPONSE_OK) {
                log.info("极光推送成功：pushPayload={}", pushPayload);
            }
        } catch (APIConnectionException | APIRequestException e) {
            log.error("极光推送失败：pushPayload={}, exception={}", pushPayload, e);
        }
        log.info("极光推送返回结果：pushResult={}" + pushResult);
        return pushResult;
    }

}
